package drbd84;

use Data::Dumper;
# vim: ts=4:sw=4:syntax=perl
# Each object here will represent a resource for drbd84 or drbd9
# The name of each resource will be appname_lvname
# linuxha.net will only deal with resources with single volumes
# for now.
#
# So for an application apache with a lvol of fs1, then
# application=apache, vg=apachevg, lvol=fs1 then the resource file might look
# like:
#
# 0resource apache_fs1 {
# 1  volume 0 {
# 2     device    /dev/debd22;
# 3     disk      /dev/apachevg/fs1;
# 4     meta-disk /dev/apachevg/fs1_meta;
# 5   }
# 6   on host1 {
# 7     address 1.2.3.4:8888;
# 8   }
# 9   on host2 {
# 10    address 1.2.3.5:8888;
# 11  }
# 12 }

sub new {
my $self={};

	my $A=[	"application","vg","lvol","host1_name","host1_ip",
		"device", "host1_port","host2_name","host2_ip","host2_port"
	];
	for my $i (@$A) {
		$self->{$i}=undef;
	}
	bless($self);
	return $self;
}

sub _check_args($$) {
my $aref=shift;
my $lref=shift;

	my @m=();
	for my $c (@$lref) {
		if(!exists($aref->{$c}) || !defined($aref->{$c})) {
			push @m,$c;
		}
	}
	return([@m]);
}

# The application is essentially what we will use as the 
# resource name...

sub set_application_storage {
my $self=shift;
my %args=@_;

	my $mm=_check_args(\%args,["device","application","vg","lvol"]);
	return(0,"Missing args: " . join(",",@$mm)) if @$mm;

	$self->{application}=$args{application};
	$self->{device}=$args{device};
	$self->{vg}=$args{vg};
	$self->{lvol}=$args{lvol};
	return(1,undef);
}

# Set the IP and ports for the application. We only need one port 
# for the application apparently (even though we will allocate
# one port per file system still in linuxha.net!)

sub set_ips {
my $self=shift;
my %args=@_;

	my $A=["host1_name","host1_ip","host1_port","host2_name","host2_ip","host2_port"];
	my $mm=_check_args(\%args,$A);
	return(0,"Missing args: " . join(",",@$mm)) if @$mm;

	for my $i (@$A) {
		if(($i eq "host1_ip" || $i eq "host2_ip") && $args{$i} =~ /\[/) {
			$args{$i}=substr($args{$i},1,length($args{$i})-2);
		}
		$self->{$i}=$args{$i};
	}
	return(1,undef);
}

# once a object has been populated with all necessary information
# this routine can be called to write the file. It will generate
# the local locally and then return the filepath so it can be
# duplicated to the other node in the cluster.

# We expect all details to be defined, otherwise abort with error.

sub write_file {
my $self=shift;
my %args=@_;

	my $A=[	"application","vg","lvol","host1_name","host1_ip",
		"device", "host1_port","host2_name","host2_ip","host2_port"
	];
	my  @m=();
	for my $i (@$A) {
		if(!exists($self->{$i}) || !defined($self->{$i})) {
			push @m,$i
		}
	}
	return(0,"Missing settings: " . join(",",@m)) if @m;
	my $fd;
	my $res="$self->{application}_$self->{vg}_$self->{lvol}";
	open($fd,">/etc/drbd.d/$res.res.tmp") or
		return(0,"Failed to write to /etc/drbd.d/$res.res.tmp: $!");
	print $fd "resource $res {\n";
	print $fd "  volume 0 {\n";
	print $fd "    device     $self->{device};\n";
	print $fd "    disk       /dev/$self->{vg}/$self->{lvol};\n";
	print $fd "    meta-disk  /dev/$self->{vg}/$self->{lvol}_meta;\n";
	print $fd "  }\n";
	print $fd "  on $self->{host1_name} {\n";
	if($self->{host1_ip} =~ /:/) {
		print $fd "    address    ipv6 [$self->{host1_ip}]:$self->{host1_port};\n";
	} else {
		print $fd "    address    $self->{host1_ip}:$self->{host1_port};\n";
	}
	print $fd "  }\n";
	print $fd "  on $self->{host2_name} {\n";
	if($self->{host2_ip} =~ /:/) {
		print $fd "    address    ipv6 [$self->{host2_ip}]:$self->{host2_port};\n";
	} else {
		print $fd "    address    $self->{host2_ip}:$self->{host2_port};\n";
	}
	print $fd "  }\n";
	if(!print $fd "}\n") {
		my $err=$!;
		close($fd);
		unlink("/etc/drbd.d/$res.res.tmp");
		return(0,"Failed to write to /etc/drbd.d/$res.res.tmp: $err");
	}
	close($fd);
	if(!rename("/etc/drbd.d/$res.res.tmp","/etc/drbd.d/$res.res")) {
		my $err=$!;
		unlink("/etc/drbd.d/$res.res.tmp");
		return(0,"Failed to rename file /etc/drbd.d/$res.res.tmp: $err");
	}
	return(1,"/etc/drbd.d/$res.res");
}

# Simply set the application and call this to load in the local
# configuration based on the resource file...

sub populate {
my $self=shift;
my %args=@_;

	my $mm=_check_args(\%args,["file"]);
	return(0,"Missing args: " . join(",",@$mm)) if @$mm;

	my $file=$args{file};
	if(! -f $file) {
		return(0,"File '$file' does not exist.");
	}
	my $fd;
	if(!open($fd,$file)) {
		return(0,"Failed to open '$file': $!");
	}
	my $s=0;
	my $f=undef;
	while(my $cline=<$fd>) {
		chomp $cline;
		$cline=~ s/^\s+//;
		$cline=~ s/\s+$//;
		next if !length($cline);
		next if $cline =~ /^#/;
		if($s==0) {
			my ($res)=($cline =~ /resource\s+(\S+)/);
			if(!$res) {
				$f="Resource line not found"; last;
			}
			my ($a,$b)=($res =~ /^([^_]+)_(.*)/);
			if(!$a || !$b) {
				$f="Resource not in expected naming format."; last;
			}
			$self->{application}=$a;
			$self->{lvol}=$b;
			$s++;
			next;
		}
		if($s==1) {
			if($cline =~ /^volume\s+0\s+\{/) {
				$s++; next;
			}
			$f="Expected volume line not found."; last;
		}
		if($s==2) {
			my ($dev)=($cline =~ /^device\s+(\S+)/);
			if(!$dev) {
				$f="Expected device line not found."; last;
			}
			$self->{device}=$dev;
			$s++; next;
		}
		if($s==3) {
			my ($vg,$lvol)=($cline =~ /^disk\s+.*\/(.*?)\/(.*?)$/);
			if(!$vg || !$lvol) {
				$f="disk line not defined correctly."; last;
			}
			$self->{vg}=$vg;
			$self->{lvol}=$lvol;
			$s++; next;
		}
		if($s==4) {
			my ($vg,$lvol)=($cline =~ /^meta-disk\s+.*\/(.*?)\/(.*?)$/);
			if(!$vg || !$lvol) {
				$f="meta-disk line not defined correctly."; last;
			}
			$s++; next;
		}
		if($s==5 || $s==8 || $s==11 || $s==12) {
			if($cline =~ /^\}$/) {
				$s++; next;
			}
			$f="Missing end quote."; last;
		}
		if($s==6 || $s==9) {
			my ($h)=($cline =~ /^on\s+(\S+)\s+\{$/);
			if(!$h) {
				$f="Invalid form for on host line."; last;
			}
			$self->{host1_name}=$h if $s==6;
			$self->{host2_name}=$h if $s==9;
			$s++; next;
		}
		if($s==7 || $s==10) {
			my ($ip,$port);
			if($cline =~ /\[/) { #ipv6 format
				($ip,$port)=($cline =~ /^address\s+ipv6\s+\[(.*)\]:(\d+);$/);
			} else { # ipv4 format
				($ip,$port)=($cline =~ /^address\s+(.*):(\d+);$/);
			}
			if(!$ip || !$port) {
				$f="Invalid ip:port in address line."; last;
			}
			if($s==7) {
				$self->{host1_ip}=$ip;
				$self->{host1_port}=$port;
			} else {
				$self->{host2_ip}=$ip;
				$self->{host2_port}=$port;
			}
			$s++; next;
		}
		if($s>12) {
			$f="Invalid data found at end of resource file."; last;
		}
	}
	close($fd);
	if($f) {
		$s++;
		return(0,"Error on line $s: $f");
	}
	return(1,undef);
}
# Compares the settings in this object to another - so can see
# if they have changed and re-create object resource file if necessary.

sub same_as {
my $self=shift;
my %args=@_;

	my $mm=_check_args(\%args,["other"]);
	return(0,"Missing args: " . join(",",@$mm)) if @$mm;

	my $A=[	"application","vg","lvol","host1_name","host1_ip",
		"device", "host1_port","host2_name","host2_ip","host2_port"
	];
	my $o=$args{other};
	
	for my $ca (@$A) {
		if(!exists($self->{$ca})) {
			return(1,0); # diff if anything missing
		}
		if(!exists($o->{$ca})) {
			return(1,0); # diff if anything missing
		}
		if(!defined($self->{$ca}) && defined($o->{$ca})) {
			return(1,0); # diff is something not defined
		}
		if(!defined($o->{$ca}) && defined($self->{$ca})) {
			return(1,0); # diff is something not defined
		}
		if($o->{$ca} ne $self->{$ca}) {
			return(1,0);
		}
	}
	return(1,1);
}

1;

